#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Filename: common_code.py
# Author: ChenChangSong
# Date: 2025-08-24

"""
功能描述: 公共代码，如日志配置、请求封装等
"""
import base64
import configparser
import io
import json
import logging
import os
import random
import sys
import time
import logging.config
from logging import config as logging_config

import requests
from openpyxl import load_workbook
from requests.exceptions import HTTPError, RequestException, Timeout
from pathlib import Path
import sys

# 获取程序运行时的目录
if getattr(sys, 'frozen', False):
    # 当程序被打包成可执行文件时，获取可执行文件所在目录
    app_dir = Path(sys.executable).parent
else:
    # 当程序在开发环境中运行时，获取项目根目录
    project_root = Path(__file__).resolve().parents[1]
    app_dir = project_root

# 使用app_dir作为基准目录来构建文件路径
log_conf_path = app_dir / 'files' / 'config_file' / 'log.conf'
excel_conf_path = app_dir / 'files' / 'config_file' / 'confg.xlsx'
log_file_path = app_dir / 'files' / 'run_log' / 'run_log.log'

# 确保日志目录存在
log_dir = app_dir / 'files' / 'run_log'
if not os.path.exists(log_dir):
    os.makedirs(log_dir, exist_ok=True)


# 读取日志配置文件
config = configparser.ConfigParser()
config.read(log_conf_path, encoding='utf-8')

# 确保formatters部分存在
if not 'formatters' in config:
    config.add_section('formatters')
    config.set('formatters', 'keys', 'form0,form1')
    config.add_section('formatter_form0')
    config.set('formatter_form0', 'format', '%(asctime)s--%(filename)s--[line:%(lineno)s]--%(levelno)s--%(levelname)s--%(message)s')
    config.add_section('formatter_form1')
    config.set('formatter_form1', 'format', '%(asctime)s--%(filename)s--[line:%(lineno)s]--%(levelno)s--%(levelname)s--%(message)s')

# 确保loggers部分存在
if not 'loggers' in config:
    config.add_section('loggers')
    config.set('loggers', 'keys', 'root')
    config.add_section('logger_root')
    config.set('logger_root', 'level', 'INFO')
    config.set('logger_root', 'handlers', 'streamhandler,filehandler')

# 设置日志文件路径
if 'handler_filehandler' in config:
    config.set('handler_filehandler', 'args', f'(r"{log_file_path}","a+","utf-8")')
else:
    # 如果没有handler_filehandler部分，添加它
    if not 'handlers' in config:
        config.add_section('handlers')
    handlers_keys = config.get('handlers', 'keys', fallback='')
    if 'filehandler' not in handlers_keys:
        config.set('handlers', 'keys', handlers_keys + ',filehandler' if handlers_keys else 'filehandler')
    
    config.add_section('handler_filehandler')
    config.set('handler_filehandler', 'class', 'FileHandler')
    config.set('handler_filehandler', 'formatter', 'form1')
    config.set('handler_filehandler', 'args', f'(r"{log_file_path}","a+","utf-8")')
    
    # 确保streamhandler存在
    if not 'handler_streamhandler' in config:
        config.add_section('handler_streamhandler')
        config.set('handler_streamhandler', 'class', 'StreamHandler')
        config.set('handler_streamhandler', 'formatter', 'form0')
        config.set('handler_streamhandler', 'args', '(sys.stdout,)')
        
        # 确保streamhandler被添加到handlers的keys中
        handlers_keys = config.get('handlers', 'keys', fallback='')
        if 'streamhandler' not in handlers_keys:
            config.set('handlers', 'keys', handlers_keys + ',streamhandler' if handlers_keys else 'streamhandler')

# 将配置写入内存
config_stream = io.StringIO()
config.write(config_stream)
config_stream.seek(0)

# 设置日志记录
logging_config.fileConfig(config_stream)
logger = logging.getLogger()

# 为了方便使用，创建info和error方法，默认使用comm_log记录器
def log_to_ui_info(message: str) -> None:
    """记录只显示在界面上的信息日志"""
    logger = logging.getLogger('comm_log')
    logger.info(message)
    
def log_to_ui_error(message: str) -> None:
    """记录只显示在界面上的错误日志"""
    logger = logging.getLogger('comm_log')
    logger.error(message)

# 保留原有的comm_log记录器，用于向后兼容
comm_log = logging.getLogger('comm_log')
comm_log.setLevel(logging.INFO)
# 移除可能存在的处理器，避免重复
for handler in comm_log.handlers[:]:
    comm_log.removeHandler(handler)
# 不传播到root logger
comm_log.propagate = False  # 防止comm_log的日志传播到root logger

def get_excel_data():
    # excel配置信息
    wb = load_workbook(excel_conf_path)
    ws = wb.active
    excel_headers = [ws.cell(row=1, column=i).value for i in range(1, ws.max_column + 1)]
    excel_row_data = [ws.cell(row=2, column=i).value for i in range(1, ws.max_column + 1)]
    excel_data = dict(zip(excel_headers, excel_row_data))  # 转换为字典
    return excel_data

comm_excel_data = get_excel_data()
# 原始字符串
original_string = f"{comm_excel_data['API帐号']}:{comm_excel_data['API密钥']}"

# 将字符串转换为字节
string_bytes = original_string.encode('ascii')
# 进行 Base64 编码
encoded_bytes = base64.b64encode(string_bytes)
# 将编码后的字节转换为字符串
auth_key = encoded_bytes.decode('ascii')

headers_json = {
    'Authorization': f'Basic {auth_key}',
    'Content-Type': 'application/json',
    'Cookie': comm_excel_data['Cookie'],
    }

headers_urlencoded = {
    'Authorization': f'Basic {auth_key}',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Cookie': comm_excel_data['Cookie']
}

def clear_log():
    # 使用与其他路径一致的格式
    filename = log_file_path
    # 确保日志目录存在
    if not os.path.exists(log_dir):
        os.makedirs(log_dir, exist_ok=True)
    with open(filename, "w") as file:
        pass
    print(f"已清空上一次执行产生的日志")

def api_request(method, url, headers=None, data=None,json=None, params=None, timeout=30,
                     verify=True, return_raw=False, **kwargs):
    """
    发送API请求并处理响应

    Args:
        method (str): HTTP请求方法，如 'GET', 'POST', 'PUT', 'DELETE'
        url (str): 请求URL
        headers (dict, optional): 请求头
        data (dict, optional): 要发送的表单数据（会被序列化为form-encoded格式）
        json (dict, optional): 要发送的JSON数据（会自动设置Content-Type为application/json）
        params (dict, optional): URL查询参数
        timeout (int, optional): 请求超时时间（秒）
        verify (bool, optional): 是否验证SSL证书
        return_raw (bool, optional): 是否返回原始响应对象

    Returns:
        dict, str or Response: 成功时返回JSON数据（或文本），失败时返回None
    """


    # 规范化请求方法大写
    method = method.upper()

    # 记录请求信息

    logger.info(f"开始API请求 - {method} {url}")
    if headers:
        logger.info(f"请求头: 略......")
        # logger.info(f"请求头: {headers}")
    if data:
        logger.info(f"请求数据: 略......")
        # logger.info(f"请求数据: {data}")
    if json:
        logger.info(f"请求参数: 略......")
        # logger.info(f"请求参数: {json}")
    if params:
        logger.info(f"请求参数: 略......")
        # logger.info(f"请求参数: {params}")

    try:
        # 根据请求方法调用相应的requests函数
        if method == 'GET':
            response = requests.get(url, headers=headers, params=params,json=json,data=data,
                                    timeout=timeout, verify=verify, **kwargs)
        elif method == 'POST':
            response = requests.post(url, headers=headers, params=params,json=json,data=data,
                                    timeout=timeout, verify=verify, **kwargs)
        elif method == 'PUT':
            response = requests.put(url, headers=headers, params=params,json=json,data=data,
                                    timeout=timeout, verify=verify, **kwargs)
        elif method == 'DELETE':
            response = requests.delete(url, headers=headers, params=params,json=json,data=data,
                                    timeout=timeout, verify=verify, **kwargs)
        else:
            logger.error(f"不支持的请求方法: {method}")
            return None,None

        # 初始化响应码
        try:
            response_code = response.status_code
        except Exception as e:
            logger.error(f"获取响应码失败: {e}")
            response_code = None

        # 检查HTTP状态码
        try:
            response.raise_for_status()
        except HTTPError as e:
            logger.error(f"HTTP错误 - 状态码: {response_code}")
            logger.error(f"错误响应内容: {response.text}")
            return response_code, None

        # 记录响应状态
        logger.info(f"响应成功 - 状态码: {response_code}")
        logger.debug(f"响应头: {response.headers}")

        # 根据内容类型返回不同格式的响应
        if 'application/json' in response.headers.get('Content-Type', ''):
            json_data = response.json()
            logger.debug(f"响应JSON数据: {json_data}")
            return response_code,response if return_raw else json_data
        else:
            text_data = response.text
            logger.debug(f"响应文本数据: {text_data[:500]}...略")
            return response_code,response if return_raw else text_data

    except Timeout as e:
        logger.error(f"请求超时: {e}")
        return response_code,None
    except HTTPError as e:
        logger.error(f"HTTP错误 - 状态码: {response.status_code}")
        logger.debug(f"错误响应内容: {response.text[:500]}...略")
        return response_code,None
    except RequestException as e:
        error_msg = str(e)
        logger.error(f"请求异常: {error_msg}")
        # 将错误信息存储在response_code中以便上层函数判断是否需要重试
        return error_msg,None
    except ValueError as e:  # JSON解析错误
        logger.error(f"JSON解析错误: {e}")
        logger.debug(f"原始响应内容: {response.text[:500]}...略")
        return response_code,response.text if return_raw else None
    except Exception as e:
        logger.error(f"未知错误: {e}")
        return response_code,None
    finally:
        logger.info(f"API请求处理完成")

# 请求函数中添加重试逻辑
def api_request_with_retry(method, url, headers=None, params=None, data=None, max_retries=3, timeout=30):

    retry_count = 0
    
    # 定义需要重试的错误类型
    retryable_errors = [
        'Connection aborted',  # 连接中止错误
        'ConnectionResetError',  # 连接重置错误
        'Connection refused',  # 连接拒绝
        'RemoteDisconnected',  # 远程断开连接
        'SSL: MAX_RECORDS_INTERNAL_ERROR'  # SSL错误
    ]
    
    while retry_count < max_retries:
        try:
            response_code, response = api_request(
                method=method, 
                url=url, 
                headers=headers, 
                params=params, 
                data=data,
                timeout=timeout  # 传递超时参数
            )
            
            # 检查是否需要重试
            if response_code != 429 and (response is not None or retry_count == max_retries - 1):
                # 如果不是限流错误，且有响应或已是最后一次尝试，则返回
                return response_code, response
                
        except Exception as e:
            # 捕获所有异常，记录错误信息
            logger.error(f"请求异常: {e}")
            response_code, response = None, None
        
        # 判断是否是需要重试的错误
        error_str = str(response_code) if response is None else ""
        should_retry = False
        
        # 检查是否是429限流错误
        if response_code == 429:
            should_retry = True
        # 检查是否是字符串类型的错误信息
        elif isinstance(response_code, str):
            should_retry = any(error in response_code for error in retryable_errors)
        
        if should_retry and retry_count < max_retries - 1:
            # 指数退避策略
            wait_time = (2 ** retry_count) + random.random()
            error_type = "API限流" if response_code == 429 else "网络错误"
            logger.warning(f"{error_type}，等待 {wait_time:.2f} 秒后重试 (尝试 {retry_count + 2}/{max_retries})")
            time.sleep(wait_time)
        
        retry_count += 1
    
    return response_code, response  # 返回最后一次请求的结果   

if __name__ == "__main__":
    clear_log()

    # 例
    url = f"https://api.tapd.cn/tcases/count?workspace_id={comm_excel_data['项目ID']}"
    params={
            "category_id": '1148433040001000130', # 用例目录ID
        }
    response_code,response = api_request(
        method='GET',
        url=url,
        headers=headers_json, # headers_json or headers_form
        params=params
    )
    print("GET响应:", response)